package com.hive;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.apache.calcite.adapter.java.ReflectiveSchema;
import org.apache.calcite.adapter.jdbc.JdbcSchema;
import org.apache.calcite.avatica.com.fasterxml.jackson.databind.node.JsonNodeFactory;
import org.apache.calcite.avatica.com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.calcite.config.CalciteConnectionConfigImpl;
import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.config.Lex;
import org.apache.calcite.jdbc.CalciteConnection;
import org.apache.calcite.jdbc.CalcitePrepare;
import org.apache.calcite.prepare.CalciteCatalogReader;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.schema.Schema;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.server.CalciteServerStatement;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.tools.FrameworkConfig;
import org.apache.calcite.tools.Frameworks;
import org.apache.hadoop.hbase.util.Triple;
import org.datanucleus.store.rdbms.datasource.dbcp.BasicDataSource;

/**
 * ClassName CalciteDemo Description TODO
 *
 * @author qq
 * @date 2023/7/27 10:27
 * @since JDK 1.8
 */
public class CalciteDemo {

  public static void main(String[] args) throws SqlParseException {
    String sql = "select * from renren_security.sys_user where user_id = 1 order by user_id";
    valid(sql);
  }

  public static class TestSchema {

    public final Triple[] rdf = {new Triple("s", "p", "o")};

  }

  public static void valid(String sql) throws SqlParseException {

    SchemaPlus schemaPlus = Frameworks.createRootSchema(true);

    schemaPlus.add("T", new ReflectiveSchema(new TestSchema()));
    Frameworks.ConfigBuilder configBuilder = Frameworks.newConfigBuilder();
    configBuilder.defaultSchema(schemaPlus);

    FrameworkConfig frameworkConfig = configBuilder.build();

    SqlParser.ConfigBuilder paresrConfig = SqlParser.configBuilder(
        frameworkConfig.getParserConfig());
    SqlParser.Config config = paresrConfig.setCaseSensitive(false).build();

    // 解析配置 - mysql设置
    SqlParser.Config mysqlConfig = SqlParser.configBuilder().setLex(Lex.MYSQL).build();
    // 创建解析器
    SqlParser parser = SqlParser.create(sql, mysqlConfig);
    // Sql语句
    // 解析sql
    SqlNode sqlNode = parser.parseQuery(sql);

    Statement stat = getStatement();
    CalcitePrepare.Context prepareContext = null;
    try {
      CalciteServerStatement cstat = stat.unwrap(CalciteServerStatement.class);
      prepareContext = cstat.createPrepareContext();
    } catch (SQLException e) {
      e.printStackTrace();
    }
//这里需要注意大小写问题，否则表会无法找到
    Properties properties = new Properties();
    properties.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(),
        String.valueOf(config.caseSensitive()));

    SqlTypeFactoryImpl factory = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
    CalciteCatalogReader calciteCatalogReader = new CalciteCatalogReader(
        prepareContext.getRootSchema(),
        prepareContext.getDefaultSchemaPath(),
        factory,
        new CalciteConnectionConfigImpl(properties));
// 校验（包括对表名，字段名，函数名，字段类型的校验。）
//    DataLineageFromSqlValidator validator = new DataLineageFromSqlValidator(SqlStdOperatorTable.instance(),
//        calciteCatalogReader, factory, SqlValidator.Config.DEFAULT
//    );
    SqlValidator validator = SqlValidatorUtil.newValidator(SqlStdOperatorTable.instance(),
        calciteCatalogReader, factory,SqlValidator.Config.DEFAULT);
// 校验后的SqlNode
    SqlNode validateSqlNode = validator.validate(sqlNode);


  }

  /**
   * @param sn  解析sql语法结构树
   * @param ijn Jackson objectnode ,递归调用
   * @return Jackson objectnode ,递归调用
   */
  private static ObjectNode nodeTree(SqlNode sn, ObjectNode ijn) {
    ObjectNode jn = ijn;
    Method[] mtd = sn.getClass().getDeclaredMethods();
    for (int i = 0; i < mtd.length; i++) {
      try {
        if (!(mtd[i].getName().startsWith("get") && mtd[i].getParameterCount() == 0)
            || mtd[i].getName().equals("getModifierNode") || mtd[i].invoke(sn) == null) {
          continue;
        }
        if (sn.getClass().getName().equals("org.apache.calcite.sql.SqlBasicCall")) {
          try {
            Object[] obj = (Object[]) mtd[i].invoke(sn);
            ObjectNode njn = new JsonNodeFactory(false).objectNode();
            if (obj[0].getClass().getName().equals("org.apache.calcite.sql.SqlIdentifier")) {
              jn.put("getTable", obj[0].toString());
            } else {
              jn.putPOJO("getSubSelect", nodeTree((SqlNode) obj[0], njn));
            }
            jn.put("getAlias", obj[1].toString());
          } catch (ClassCastException e) {
            e.printStackTrace();
          }
        }
        Object obj = mtd[i].invoke(sn);
        String nm = obj.getClass().getName();
        String op = "";
        if (nm.equals("org.apache.calcite.sql.SqlBasicCall")) {
          Method mt = obj.getClass().getMethod("getOperator");
          op = mt.invoke(obj).toString();
          System.out.println(mtd[i].invoke(sn).getClass().getName());
        }
        if ("org.apache.calcite.sql.SqlKind".equals(nm) || nm.equals(
            "org.apache.calcite.sql.SqlLiteral") || (
            "org.apache.calcite.sql.SqlBasicCall".equals(nm) && !op.equals("AS"))
            || "org.apache.calcite.sql.SqlIdentifier".equals(
            nm) || "org.apache.calcite.sql.SqlNodeList".equals(
            nm)) {
          jn.put(mtd[i].getName(), obj.toString());
        } else {
          ObjectNode njn = new JsonNodeFactory(false).objectNode();
          jn.putPOJO(mtd[i].getName(), nodeTree((SqlNode) obj, njn));
        }
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      } catch (ClassCastException e) {
        e.printStackTrace();
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      }
    }
    return jn;
  }


  public static Statement getStatement() {
    Properties info = new Properties();
    info.setProperty("lex", "JAVA");
    CalciteConnection calciteConnection = null;
    Statement stat = null;
    try {
      Class.forName("org.apache.calcite.jdbc.Driver");
      Connection connection =
          DriverManager.getConnection("jdbc:calcite:", info);
      calciteConnection =
          connection.unwrap(CalciteConnection.class);
      SchemaPlus rootSchema = calciteConnection.getRootSchema();
      //创建Mysql的数据源schema
      Class.forName("com.mysql.jdbc.Driver");
      BasicDataSource dataSource = new BasicDataSource();
      dataSource.setUrl(
          "jdbc:mysql://localhost:3306/renren_security?useUnicode=true&characterEncoding=UTF-8&remarks=true&useInformationSchema=true&useSSL=false");
      dataSource.setUsername("root");
      dataSource.setPassword("123456");
      Schema schema = JdbcSchema.create(rootSchema, "renren_security", dataSource,
          null, null);
      rootSchema.add("renren_security", schema);
      stat = calciteConnection.createStatement();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (SQLException e) {
      e.printStackTrace();
    }
    return stat;
  }
}
